﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RayTracerLib.Engine;
using Microsoft.DirectX;
using System.Reflection;
using CommonLib.Geometry;
using CommonLib.Materials;
using CommonLib.Primitives;
using CommonLib.Engine;

namespace RayTracerLib.Reader
{
    /// <summary>
    /// Символы SDL (Scene Description Language)
    /// </summary>
    public enum Symbols
    {
        Unknow, Sphere, Cube, Def, Use, Diffuse, Specular, Reflectivity, SpecularExponent,
        Translate, Rotate, Scale, LeftCurly, RightCurly, End, Light, Ambient, BackGround, IdentityAffine
    }
    /// <summary>
    /// Проверяет сцену на валидность
    /// </summary>
    public class Validator
    {
        private int ind_char = 0;//текущее положение в строке
        private Matrix _matrix;
        private Material _material;

        public string VerifiedString { get; set; }
        public List<Error> Errors { get; set; }
        public Dictionary<string, Material> DefinitionMaterials { get; set; }
        public Scene Scene { get; set; }
        public Validator(string verified_str)
        {
            VerifiedString = verified_str.Trim();
            Errors = new List<Error>();
            DefinitionMaterials = new Dictionary<string, Material>();
            _matrix = new Matrix();
            _matrix.M11 = 1;
            _matrix.M22 = 1;
            _matrix.M33 = 1;
            _matrix.M44 = 1;
            _material = new Material();
            Scene = new Scene();
        }
        private Symbols StringToEnum(string symbol)
        {
            Type t = typeof(Symbols);
            FieldInfo[] fields = t.GetFields();
            foreach (FieldInfo field in fields)
                if (field.Name.Equals(symbol, StringComparison.OrdinalIgnoreCase))
                    return (Symbols)Enum.Parse(typeof(Symbols), symbol, true);
            if (symbol == "{") return Symbols.LeftCurly;
            if (symbol == "}") return Symbols.RightCurly;
            return Symbols.Unknow;
        }
        private string GetNextSymbol()
        {
            string symbol = "";
            while (ind_char < VerifiedString.Length)
            {
                switch (VerifiedString[ind_char])
                {
                    case '\a':
                    case '\r':
                    case '\t':                    
                    case '\b':
                    case '\v':
                    case '\f':
                    case '\n':
                    case ' ':
                        if (symbol != string.Empty)
                            return symbol;
                        break;
                    case '!'://комментарий, пропускаем все до конца строки
                        while (VerifiedString[ind_char] != '\n')
                            ind_char++;
                        break;
                    case '{':
                    case '}':
                        return VerifiedString[ind_char++].ToString();
                    default:
                        symbol += VerifiedString[ind_char];
                        if ((ind_char + 1) < VerifiedString.Length && (VerifiedString[ind_char + 1] == '{' || VerifiedString[ind_char + 1] == '}'))
                        {
                            ind_char++;
                            return symbol;
                        }
                        break;

                }
                ind_char++;
            }
            if (symbol == "")
                return "End";
            else
                return symbol;
        }
        private bool GetNextFloat(out float result)
        {
            string symbol = GetNextSymbol();
            result = 0;
            symbol = symbol.Replace('.', ',');
            if (!float.TryParse(symbol, out result))
            {
                Errors.Add(new Error("Ожидалось число", ind_char - symbol.Length));
                return false;
            }
            return true;
        }
        private bool GetIdentifier(ref string result)
        {
            result = GetNextSymbol().ToLower();
            if (char.IsDigit(result[0]))
            {
                Errors.Add(new Error("Ожидался идентификатор", ind_char - result.Length));
                result = string.Empty;
                return false;
            }
            for (int i = 1; i < result.Length; i++)
                if (!(char.IsLetterOrDigit(result[i]) || result[i] == '_'))
                {
                    Errors.Add(new Error("Ожидался идентификатор", ind_char - result.Length));
                    result = string.Empty;
                    return false;
                }
            return true;
        }

        private Matrix CreateRotationMatrix(float angle, float x, float y, float z)
        {
            Matrix m = new Matrix();
            m.M11 = (float)(Math.Cos(angle) + (1 - Math.Cos(angle)) * x * x);
            m.M12 = (float)((1 - Math.Cos(angle)) * x * y - Math.Sin(angle) * z);
            m.M13 = (float)((1 - Math.Cos(angle)) * x * z + Math.Sin(angle) * y);
            m.M21 = (float)((1 - Math.Cos(angle)) * x * y + Math.Sin(angle) * z);
            m.M22 =(float)( Math.Cos(angle)+(1 - Math.Cos(angle))*y*y);
            m.M23 = (float)((1 - Math.Cos(angle)) * y * z - Math.Sin(angle) * x);
            m.M31 = (float)((1-Math.Cos(angle))*z*x - Math.Sin(angle)*y);
            m.M32 = (float)((1 - Math.Cos(angle))*z*y + Math.Sin(angle)*x);
            m.M33 = (float)(Math.Cos(angle) + (1-Math.Cos(angle))*z*z);
            m.M44 = 1;
                return m;
        }
        public bool Validate()
        {
            if (VerifiedString == String.Empty)
            {
                Errors.Add(new Error("Сцена не описана", 0));
                return false;
            }
            string symbol = GetNextSymbol();
            while (StringToEnum(symbol) != Symbols.End)
            {
                switch (StringToEnum(symbol))
                {
                    case Symbols.Cube:
                        Cube cube = new Cube();
                        cube.TransformMatrix = _matrix;
                        cube.MaterialShape = _material.Clone();
                        Scene.AddPrimitive(cube);
                        break;
                    case Symbols.Sphere:
                        Sphere sphere = new Sphere();
                        sphere.TransformMatrix = _matrix;
                        sphere.MaterialShape = _material.Clone();
                        Scene.AddPrimitive(sphere);
                        break;
                    case Symbols.BackGround:
                        Color clr = new Color();
                        if (GetNextFloat(out clr.Red) && GetNextFloat(out clr.Green) && GetNextFloat(out clr.Blue))                        
                            Scene.Background = clr;                        
                        break;
                    case Symbols.IdentityAffine:
                        _matrix = new Matrix();
                        _matrix.M11 = _matrix.M22 = _matrix.M33 = _matrix.M44 = 1;
                        break;
                    case Symbols.Light:
                        Point3D pos = new Point3D();
                        if (GetNextFloat(out pos.X) && GetNextFloat(out pos.Y) && GetNextFloat(out pos.Z))
                        {
                            clr = new Color();
                            if (GetNextFloat(out clr.Red) && GetNextFloat(out clr.Green) && GetNextFloat(out clr.Blue))
                            {
                                Light light = new Light(pos, clr);
                                Scene.AddLight(light);
                            }
                        }
                        break;
                    case Symbols.Def:
                        string ident = "";
                        if (GetIdentifier(ref ident))
                        {
                            if (DefinitionMaterials.ContainsKey(ident))
                            {
                                Errors.Add(new Error("Переопределение идентификатора", ind_char - symbol.Length));
                                break;
                            }
                            if (StringToEnum(GetNextSymbol()) != Symbols.LeftCurly)
                            {
                                Errors.Add(new Error("Ожидался символ {", ind_char - symbol.Length));
                                break;
                            }
                            int start = ind_char;
                            Symbols s = StringToEnum(GetNextSymbol());
                            while (s != Symbols.RightCurly && s != Symbols.End)
                                s = StringToEnum(GetNextSymbol());
                            if (s == Symbols.End)
                            {
                                Errors.Add(new Error("Ожидался символ }", ind_char - symbol.Length));
                                break;
                            }
                            int finish = ind_char;
                            string inner_text = VerifiedString.Substring(start, finish - start - 1);
                            Validator def_validator = new Validator(inner_text);
                            Material material = new Material();
                            def_validator.ValidateMaterial(ref material);
                            def_validator.Errors.ForEach(o => o.IndexCharPosition += start);
                            DefinitionMaterials.Add(ident, material);
                            Errors.AddRange(def_validator.Errors);
                        }
                        break;
                    case Symbols.Translate:
                        Vector3 v = new Vector3();
                        if (GetNextFloat(out v.X) && GetNextFloat(out v.Y) && GetNextFloat(out v.Z))
                        {
                            Matrix m = new Matrix();
                            m.Translate(v);
                             m.Multiply(_matrix);
                             _matrix = m;
                            //_matrix.Multiply(m);
                        }
                        break;
                    case Symbols.Rotate:
                        float angle = 0;
                        if (GetNextFloat(out angle) && GetNextFloat(out v.X) && GetNextFloat(out v.Y) && GetNextFloat(out v.Z))
                        {
                            //Matrix m2 = CreateRotationMatrix((float)Math.PI * angle / 180, v.X, v.Y, v.Z);
                            Matrix m = new Matrix();
                            m.RotateAxis(v, (float)Math.PI * angle / 180);
                             m.Multiply(_matrix);
                             _matrix = m;
                            //_matrix.Multiply(m);
                        }
                        break;
                    case Symbols.Scale:
                        if (GetNextFloat(out v.X) && GetNextFloat(out v.Y) && GetNextFloat(out v.Z))
                        {
                            Matrix m = new Matrix();
                            m.Scale(v);
                            m.Multiply(_matrix);
                            _matrix = m;
                            //_matrix.Multiply(m);
                        }
                        break;
                    case Symbols.Use:
                        ident = "";
                        if (GetIdentifier(ref ident))
                        {
                            if (DefinitionMaterials.ContainsKey(ident))
                                _material = DefinitionMaterials[ident];
                            else
                                Errors.Add(new Error("Неизвестный идентификатор", ind_char - symbol.Length));
                        }
                        break;
                    case Symbols.Unknow:
                        Errors.Add(new Error("Неизвестный символ", ind_char - symbol.Length));
                        break;
                }
                symbol = GetNextSymbol();
            }
            return Errors.Count == 0;
        }

        private void ValidateMaterial(ref Material material)
        {
            string symbol = GetNextSymbol();
            while (StringToEnum(symbol) != Symbols.End)
            {
                switch (StringToEnum(symbol))
                {
                    case Symbols.Reflectivity:
                        GetNextFloat(out material.Reflectivity);
                        break;
                    case Symbols.SpecularExponent:
                        GetNextFloat(out material.SpecularExponent);
                        break;
                    case Symbols.Ambient:
                        Color clr = new Color();
                        if (GetNextFloat(out clr.Red) && GetNextFloat(out clr.Green) && GetNextFloat(out clr.Blue))
                            material.AmbientColor = clr;
                        break;
                    case Symbols.Diffuse:
                        clr = new Color();
                        if (GetNextFloat(out clr.Red) && GetNextFloat(out clr.Green) && GetNextFloat(out clr.Blue))
                            material.DiffuseColor = clr;
                        break;
                    case Symbols.Specular:
                        clr = new Color();
                        if (GetNextFloat(out clr.Red) && GetNextFloat(out clr.Green) && GetNextFloat(out clr.Blue))
                            material.SpecularColor = clr;
                        break;
                    case Symbols.Unknow:
                        Errors.Add(new Error("Неизвестный символ", ind_char - symbol.Length));
                        break;
                }
                symbol = GetNextSymbol();
            }
        }
    }
}

